/**
 * File: VelvetProcessor.java
 * Created by: mhaimel
 * Created on: 23 Jul 2009
 * CVS:  $Id: VelvetProcessor.java,v 1.2 2009/10/09 15:12:39 mhaimel Exp $
 */
package uk.ac.ebi.curtain.processor.velvet.impl;

import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.NotImplementedException;

import uk.ac.ebi.curtain.controller.velvet.VelvetInputBean;
import uk.ac.ebi.curtain.controller.velvet.VelvetOutputBean;
import uk.ac.ebi.curtain.processor.AbstractProcessor;
import uk.ac.ebi.curtain.processor.general.BucketReadInfo;
import uk.ac.ebi.curtain.util.FileHelper;
import uk.ac.ebi.curtain.util.VelvetExec;
import uk.ac.ebi.curtain.utils.CurtainUncheckedException;
import uk.ac.ebi.curtain.utils.data.FileType;
import uk.ac.ebi.curtain.utils.data.ReadType;
import uk.ac.ebi.curtain.utils.file.FileInfo;
import uk.ac.ebi.curtain.utils.file.Scaffold2ContigProcessor;
import uk.ac.ebi.velvet.exec.ProcessorService;
import uk.ac.ebi.velvet.exec.RuntimeExec;
import uk.ac.ebi.velvet.model.Supercontig;
import uk.ac.ebi.velvet.processor.StatisticsInfo;

import com.google.inject.Inject;

/**
 * @author mhaimel
 *
 */
public class VelvetProcessor extends AbstractProcessor<VelvetInputBean, VelvetOutputBean> {

	private ProcessorService processorService;
	private VelvetOutputBean outBean;


	@Override
	protected VelvetOutputBean buildReturnObject() {
		return outBean;
	}
	
	public void reset(){
		this.outBean = null;
		this.setInput(null);
	}

	@Override
	public void process() {
		getLog().debug("Start Velvet run for " + getInput().getBaseDir() + " ...");
		outBean = new VelvetOutputBean();
		outBean.setInputContig(Supercontig.getContigDumpFile(getInput().getBaseDir()));
		VelvetExec exec = _buildExec(getInput());
		if(getContext().getArguments().getUseContigInput()){
			addContigFiles(exec);
		}
		if(hasReadFiles(exec)){
//			exec.
			// clear in case of previous runs
			File resultDir = exec.getResultDir();
			_deleteDirectory(resultDir);
			createDirectory(resultDir);			
			StatisticsInfo stats = runVelvet(exec);
			if(null != stats){
				File contigsFile = exec.getContigsFile();
				FileInfo outFile = new FileInfo(contigsFile,FileType.fasta,ReadType.Contig);
				outBean.setOutputContig(outFile);
			} else {
				outBean = null;
			}
		} else {
			getLog().warn("No read data available to process for velvet in " + getInput().getBaseDir());
		}
		getLog().debug("Finished Velvet run for " + getInput().getBaseDir());
	}

	public StatisticsInfo runVelvet(VelvetExec exec) {
		return exec.runVelvetAll();
	}

	public void createDirectory(File resultDir) {
		if(!resultDir.mkdirs()){
			throw new CurtainUncheckedException("Problems creating directory " + resultDir);
		}
	}
	
	private void addContigFiles(VelvetExec exec) {
		FileInfo ctgFile = Supercontig.getContigDumpFile(getInput().getBaseDir());
		FileInfo longFile = new FileInfo(ctgFile.getFile(), ctgFile.getFileType(), ReadType.Long);
		exec.addFile(0, longFile);	
		
		FileInfo ctgSplitFile = Supercontig.getContigDumpFile(getInput().getBaseDir(),"split");
		FileInfo longSplitFile = new FileInfo(ctgSplitFile.getFile(), ctgFile.getFileType(), ReadType.Long);
		if(!longSplitFile.getFile().exists() || longSplitFile.getFile().length() == 0){
			Scaffold2ContigProcessor.processFiles(ctgFile,longSplitFile);			
		}
		exec.addFile(0, longSplitFile);
	}

	private boolean hasReadFiles(VelvetExec exec) {
		boolean valid = false;
		Map<Integer, Set<FileInfo>> inputFiles = exec.getReadFiles();
		Set<FileInfo> allFiles = new HashSet<FileInfo>();
		for(Set<FileInfo> fiSet : inputFiles.values()){
			allFiles.addAll(fiSet);
		}
		if(!allFiles.isEmpty()){
			for(FileInfo fi : allFiles){
				if(fi.getFile().length() > 0){
					valid = true;
				} else {
					getLog().warn("Empty file used for velvet: " + fi);
				}
			}
			if(!valid){
				getLog().warn("Only empty files provided as input for bucket " + getInput().getBaseDir());
			}
		} else {
			getLog().warn("No input files provided for bucket " + getInput().getBaseDir());			
		}
		return valid;
	}

	protected void _deleteDirectory(File dir) {
		try {
			FileUtils.deleteDirectory(dir);
		} catch (IOException e) {
			throw new CurtainUncheckedException("Not able to delete dir " + dir);
		}
	}

	private VelvetExec _buildExec(VelvetInputBean input) {
		VelvetExec exec = new MyVelvet(
				input.getBaseDir(),
				getKmer(),
				getConfig().getVelvethExec(),
				getConfig().getVelvetgExec());
		exec.setForce(getContext().getArguments().getVelvetForce().get());
		
		boolean forceUnpairedRun = getContext().getArguments().getVelvetUnpaired().get();
		
		for(Entry<FileInfo, Integer> entry : input.getInputFiles().entrySet()){
			exec.addFile(entry.getValue(), translate(entry.getKey(),forceUnpairedRun));
		}
		if(!forceUnpairedRun){
			for(Entry<Integer, Integer> entry : getContext().getArguments().getCategory2InsertLength().entrySet()){
				exec.addInsertLength(entry.getKey(), entry.getValue());
			}
			for(Entry<Integer, Integer> entry : getContext().getArguments().getCategory2InsertLengthSd().entrySet()){
				exec.addInsertLengthSd(entry.getKey(), entry.getValue());
			}
		}
		
		Set<Integer> cat = getContext().getArguments().getCategories();
		File bd = input.getBaseDir();
		
		Integer bId = FileHelper.getIdFromSubDirectory(bd);
		boolean curtainCoverageCalculation = getContext().getArguments().getVelvetCoverageCalc().get();
		if(curtainCoverageCalculation){
			double totalKmerCov = 0d;
			for(Integer i : cat){
				File oFile = Supercontig.getCategoryInfoFile(bd, i);
				if(oFile.exists()){
					BucketReadInfo info = BucketReadInfo.fromFile(bId.longValue(), i, oFile);
					double kmerCov = BucketReadInfo.calculateKmerCoverage(getKmer(), getBpCoverage(info),info.getAvgReadLength());
					totalKmerCov += kmerCov;
				}
			}		
			if(totalKmerCov > 0){
				exec.setExpectedKmerCoverage(totalKmerCov);
				exec.setExpectedKmerCutoff(totalKmerCov*0.7);
			}
		}

		if(!forceUnpairedRun){
			if(getContext().getArguments().getVelvetMinPairCount() > 0){
				exec.setMinPairCount(getContext().getArguments().getVelvetMinPairCount());
			}
		}
		return exec;
	}

	private FileInfo translate(FileInfo key, boolean forceUnpairedRun) {
		if(!forceUnpairedRun){
			return key;
		} 
		ReadType rt = key.getReadType();
		if(!rt.isPaired()){
			return key;
		}
		if(ReadType.ShortPaired.equals(rt)){
			rt = ReadType.Short;
		} else if(ReadType.LongPaired.equals(rt)){
			rt = ReadType.Long;
		} else {
			throw new NotImplementedException("Paired Read type not yet supported"+rt);
		}
		return new FileInfo(key.getFile(), key.getFileType(), rt);
	}

	private Double getBpCoverage(BucketReadInfo info) {
		return info.getWeightedMeanCoverage();
	}
	
	
	private Integer getKmer() {
		return getContext().getArguments().getKmer();
	}
	
	@Inject
	public void setProcessorService(ProcessorService processorService) {
		this.processorService = processorService;
	}

	public ProcessorService getProcessorService() {
		return processorService;
	}	


	private class MyVelvet extends VelvetExec{

		public MyVelvet(File baseDir, Integer kmer, String execH,String execG) {
			super(baseDir, kmer, Supercontig.getVelvetDirName(kmer), execH, execG);
		}
		
		@Override
		protected RuntimeExec buildRuntime(File baseDir) {
			return new RuntimeExec(baseDir,getProcessorService());
		}
	}
}
